# nodejs精选面试题

# 1、node如何开启一个http服务?

// 引入内置http模块
var http = require('http');

// 创建一个简单服务器,访问http://127.0.0.1:3000/,显示Hello World
http.createServer(function(req, res) {
    res.end('Hello World\n');
}).listen(3000, '127.0.0.1');
console.log('Server running at http://127.0.0.1:3000/');
1
2
3
4
5
6
7
8

# 2、讲一下你对node的了解

node 是基于 Chrome v8引擎的JavaScript运行环境(虚拟机);

  • 基本上是 Node.js 基于事件驱动的架构,其中 I/O 异步运行,使其轻量且高效。
  • 它也被用于开发桌面应用程序以及 electron 框架,因为它提供了 API 来访问 OS(操作系统) 级别的功能,例如文件系统、网络等。

特点:具有事件驱动,非阻塞I/O模型,高并发和轻量级,单线程,单进程特点;

# 3、Node.js 如何克服 I/O 操作阻塞的问题 ?

由于 node.js 有一个事件循环,可用于以异步方式处理所有 I/O 操作,而不会阻塞 main 函数。

  • 如果需要进行一些网络调用,它将被安排在事件循环中,而不是主线程(单线程)中。
  • 如果有多个这样的 I/O 调用,每个调用都会相应地排队分别执行(除了主线程)。

因此,即使我们有单线程 JS,I/O 操作也是以非阻塞方式处理的。

单线程:Node.js 是作为异步处理的实验显式创建的,即在单个线程上进行异步处理,而不是通过不同框架进行缩放的现有基于线程的实现。

# 4、Node.js 中有多少种 API 函数 ?

有两种类型的 API 函数:

  • 异步、非阻塞函数:主要是 I/O 操作,可以从主循环中分叉出来。
  • 同步的、阻塞的函数 :主要是影响在主循环中运行的进程的操作。

###5、 如果 Node.js 是单线程的,那么它如何处理并发?

主循环是单线程的,所有异步调用都由 libuv 库管理。

// 应用案例
const crypto = require("crypto");
const start = Date.now();
function logHashTime() {
  crypto.pbkdf2("a", "b", 100000, 512, "sha512", () => {
    console.log("Hash: ", Date.now() - start);
  });
}
logHashTime(); // 输出 1213
logHashTime(); // 输出 1225
logHashTime(); // 输出 1212
logHashTime(); // 输出 1222

// 这是因为 libuv 设置了一个线程池来处理这种并发。线程池中有多少线程取决于内核数量,但您可以覆盖它。
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 6、Node.js 中的 fork 是什么 ?

  • 通常,fork 用于生成子进程。
  • 在 node 中,它用于创建一个新的 v8 引擎实例来运行多个 worker 来执行代码。

# 7、Node.JS 中的事件循环是什么?

但凡异步函数,都由事件循环使用队列和侦听器进行管理。我们可以使用下图得到这个想法:

node事件循环

因此,当需要执行异步函数(或 I/O)时,主线程会将其发送到另一个线程,从而允许 v8 继续执行主代码。

  • 事件循环涉及不同阶段的特定任务,例如计时器、挂起的回调、空闲或准备、轮询、检查、使用不同的 FIFO 队列关闭回调。
  • 此外,在迭代之间,它会检查异步 I/O 或计时器,如果没有异步 I/O 或计时器,则会完全关闭。

# 8、node底层了解吗?

用 libuv 和一些其他库拓展了 js 在前端没有实现的功能。v8 是执行 js 代码的, libuv 是 nodejs 的底层支撑,包括事件循环,文件IO,网络IO,定时器等实现。代码的结构是 nodejs 内置js模块,比如 http,net,fs,然后内置js模块是调用c++层的代码,比如net调用h ttp://tcp_wrap.cc,然后 c++ 层调用 libuv 层的代码,libuv 完成任务后,再往回调。

# 9、什么是线程池,Node.js 中哪个库处理它 ?

线程池由 libuv 库处理。

  • libuv 是一个多平台的 C 库,它支持基于异步 I/O 的操作,例如文件系统、网络和并发。

node事件循环

# 10、v8了解多少

  • v8是谷歌开源的高性能JavaScript引擎的名称,谷歌在浏览页面时由v8负责js的处理并执行

  • v8引擎提供了JavaScript的运行环境,dom和其他web api则由浏览器来提供.

  • v8引擎独立于托管它的浏览器,基于这个关键特性 V8 于 2009 年被选为为 Node.js 提供支持的引擎

  • 底层是基于c++开发的,既能独立运行,又可以嵌入c++应用中

  • 经过不断的改进v8引擎具有高性能和跨平台的特性,且可运行于 Mac、Windows、Linux 和其他一些系统

  • v8使用了JIT编译出即时机器码,能极大提高js编译速度

  • 借鉴了java vm的垃圾回收机制,能精准回收垃圾

# 11、如何通过集群提高 Node.js 的性能?

Node.js 应用程序在单个处理器上运行,这意味着默认情况下它们不会利用多核系统。

  • 集群模式用于启动多个 node.js 进程,从而拥有多个事件循环实例。
  • 当我们开始在后台的 nodejs 应用程序中使用集群时,会创建多个 node.js 进程,但还有一个称为集群管理器的父进程,它负责监控我们应用程序各个实例的健康状况。

node事件循环

# 12、工作线程与集群有何不同 ?

集群(Cluster):

  • 每个 CPU 上都有一个进程与 IPC 进行通信。
  • 如果我们想让多个服务器通过单个端口接受 HTTP 请求,集群会很有帮助。
  • 这些进程在每个 CPU 中产生,因此将具有单独的内存和节点实例,这将进一步导致内存问题。

工作线程:

  • 总共只有一个进程有多个线程。
  • 每个线程都有一个 Node 实例(一个事件循环,一个 JS 引擎),大多数 API 都可以访问。
  • 与其他线程共享内存(例如 SharedArrayBuffer)
  • 这可用于处理数据或访问文件系统等 CPU 密集型任务,因为 NodeJS 是单线程的,同步任务可以更有效地利用工作线程。

# 13、Node.js 中的事件发射器是什么 ?

  • EventEmitter是一个 Node.js 类,它包含所有基本上能够发出事件的对象。
  • 这可以通过使用 eventEmitter.on()函数附加由对象发出的命名事件来完成。

因此,每当这个对象抛出一个甚至附加的函数时,都会同步调用。

const EventEmitter = require("events");
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on("event", () => {
  console.log("an event occurred!");
});
myEmitter.emit("event");
1
2
3
4
5
6
7

# 14、如何测量异步操作的持续时间 ?

Performance API 为我们提供了找出必要性能指标的工具,一个简单的例子是使用 async_hooks 和 perf_hooks:

"use strict";
const async_hooks = require("async_hooks");
const { performance, PerformanceObserver } = require("perf_hooks");
const set = new Set();
const hook = async_hooks.createHook({
  init(id, type) {
    if (type === "Timeout") {
      performance.mark(`Timeout-${id}-Init`);
      set.add(id);
    }
  },
  destroy(id) {
    if (set.has(id)) {
      set.delete(id);
      performance.mark(`Timeout-${id}-Destroy`);
      performance.measure(
        `Timeout-${id}`,
        `Timeout-${id}-Init`,
        `Timeout-${id}-Destroy`
      );
    }
  },
});
hook.enable();
const obs = new PerformanceObserver((list, observer) => {
  console.log(list.getEntries()[0]);
  performance.clearMarks();
  observer.disconnect();
});
obs.observe({ entryTypes: ["measure"], buffered: true });
setTimeout(() => {}, 1000);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

# 15、如何衡量异步操作的性能 ?

Performance API 为我们提供了找出必要性能指标的工具。如下:

const { PerformanceObserver, performance } = require("perf_hooks");
const obs = new PerformanceObserver((items) => {
  console.log(items.getEntries()[0].duration);
  performance.clearMarks();
});
obs.observe({ entryTypes: ["measure"] });
performance.measure("Start to Now");
performance.mark("A");
doSomeLongRunningProcess(() => {
  performance.measure("A to Now", "A");
  performance.mark("B");
  performance.measure("A to B", "A", "B");
});
1
2
3
4
5
6
7
8
9
10
11
12
13

# 16、对于 Node.js,为什么 Google 使用 V8 引擎 ?

那么,还有其他选择吗?是的,当然,我们有来自 Firefox 的Spidermonkey,来自 Edge 的 Chakra

  • 但 Google 的 v8 是发展最快的(因为它是开源的,所以有一个巨大的社区帮助开发功能和修复错误)和最快的(因为它是用 c++ 编写的)
  • 作为一个 JavaScript 和 WebAssembly 引擎。它可以移植到几乎所有已知的机器上。

# 17、为什么要把 Express 应用和服务器分开 ?

  • 服务器负责初始化路由、中间件和其他应用程序逻辑,而应用程序具有所有业务逻辑,这些业务逻辑将由服务器启动的路由提供服务。
  • 这确保了业务逻辑被封装并与应用程序逻辑解耦,从而使项目更具可读性和可维护性。

# 18、解释 Node.js 中的 Reactor 模式是什么 ?

Reactor 模式又是一种用于非阻塞 I/O 操作的模式,但总的来说,这用于任何事件驱动的架构。

这里面有两个组件:Reactor 和 Handler

  • Reactor:它的工作是将 I/O 事件分派给适当的处理程序
  • Handler:它的工作是实际处理这些事件

# 19、什么是中间件 ?

中间件介于请求和业务逻辑之间。

  • 它主要用于捕获日志并启用速率限制、路由、身份验证,基本上是任何不属于业务逻辑的部分。
  • 当然,还有第三方中间件,例如 body-parser,您可以为特定用例编写自己的中间件。

# 20、什么是 node.js 缓冲区?

通常,缓冲区是一个临时内存,流主要使用它来保存一些数据,直到被消耗为止。

  • 与 JavaScript 的 Unit8Array 相比,缓冲区还引入了其他用例,主要用于表示固定长度的字节序列。
  • 它还支持 ASCII、utf-8 等传统编码。它是 v8 之外的固定(不可调整大小)分配内存。

# 21、什么是 node.js 流 ?

Streams 是 EventEmitter 的实例,可用于处理 Node.js 中的流数据。

  • 它们可用于处理和操作网络上的流式大文件(视频、mp3 等)。他们使用缓冲区作为临时存储。

流主要有四种类型:

  • Writable:可以写入数据的流(例如,fs.createWriteStream())
  • Readable:可以从中读取数据的流(例如,fs.createReadStream())
  • Duplex:既可读又可写的流(例如,net.Socket)
  • Transform:可以在写入和读取数据时修改或转换数据的双工流(例如,zlib.createDeflate())

# 22、鉴权有了解过了?

  • HTTP Basic Authentication (HTTP基本认证)

  • session-cookie

  • Token 验证(包括JWT,SSO)

  • OAuth(开放授权)

我们普通网站常用的认证就是 session-cookie 的方式,用户向服务端发生请求,服务端会创建 session 并保存相关身份信息,并向客户端下发一个 sessionId,大家如果用心的话,会发现跟JAVA交互的时候,浏览器会有一个 JSESSION_ID,跟 PHP 交互的时候,会有一个 PHPSESSION_ID;后面的每次请求,客户端都会自动带上这个 cookie 跟服务端通信。

实际上大家要明白每一种方式的作用;SSO 主要用来做单点登录;OAuth 主要用来做第三方网站授权;JWT 就是一种便于扩展的跨域认证解决方案,通常会考察这个。

# 说说 token 登录模式

Token登录模式是一种在Web应用中进行身份验证和授权的常见方式。它基于令牌(Token)的概念,通过在客户端和服务器之间传递令牌来验证用户的身份和授权访问。

以下是Token登录模式的一般工作流程:

  1. 用户提供凭据:用户在登录页面上提供用户名和密码等凭据。

  2. 身份验证:服务器接收到用户提供的凭据后,进行身份验证。通常,服务器会检查用户名和密码是否匹配,并验证用户的身份。

  3. 生成令牌:如果身份验证成功,服务器会生成一个令牌(Token)。令牌是一个加密的字符串,其中包含有关用户身份和权限的信息。

  4. 令牌返回给客户端:服务器将生成的令牌发送回客户端作为登录凭据的一部分。通常,令牌会被存储在客户端的Cookie或本地存储中。

  5. 请求授权:客户端在后续的请求中将令牌包含在请求头、查询参数或Cookie中,并发送给服务器。

  6. 令牌验证:服务器接收到请求后,会验证令牌的有效性和完整性。服务器可以解密令牌并检查其中的信息,例如用户ID、角色、权限等。

  7. 授权访问:如果令牌验证成功,服务器会授权用户访问请求的资源或执行请求的操作。服务器可以使用令牌中的信息来确定用户的权限级别和可访问的资源。

  8. 令牌过期和刷新:令牌通常具有过期时间。当令牌过期时,客户端可以使用刷新令牌(Refresh Token)来获取新的令牌,而无需重新进行身份验证。

Token登录模式的主要优势包括:

  • 无状态:服务器不需要在后端存储用户的会话信息,因为所有必要的信息都包含在令牌中。这使得服务器可以更轻松地进行水平扩展和负载均衡。

  • 跨域支持:令牌可以在跨域请求中使用,因为令牌可以在请求头、查询参数或Cookie中传递。

  • 安全性:令牌可以使用加密算法进行签名和验证,确保令牌的完整性和安全性。此外,令牌还可以通过设置短期过期时间和使用刷新令牌来增加安全性。

  • 可扩展性:Token登录模式可以与其他身份验证和授权机制结合使用,例如多因素身份验证、单点登录(SSO)等。

总的来说,Token登录模式通过使用令牌来验证用户身份和授权访问,提供了一种安全、可扩展和无状态的身份验证机制。它已经成为现代Web应用中常见的身份验证方式之一。

# 什么是 NestJS?它有什么优点和特点?

NestJS 是一个用于构建高效、可扩展的服务器端应用程序的框架。它基于 TypeScript,并且使用了面向对象的编程(OOP)和函数式编程(FP)的概念,同时结合了 Express.js 的灵活性和 Angular 的模块化思想。以下是 NestJS 的一些优点和特点:

  1. 基于 TypeScript:NestJS 使用 TypeScript 作为主要开发语言,这使得代码更具可读性、可维护性和可扩展性。TypeScript 提供了静态类型检查和最新的 ECMAScript 特性支持,使开发过程更加高效和安全。

  2. 模块化架构:NestJS 借鉴了 Angular 的模块化思想,通过模块的概念将应用程序划分为一组功能相关的模块。这种模块化的架构使得应用程序的组织和维护更加简单和可扩展。

  3. 强大的依赖注入(DI):NestJS 提供了一个强大的依赖注入容器,可以方便地管理和组织应用程序的各个组件和服务。依赖注入使得代码解耦、可测试性更强,并且有助于实现可扩展的架构。

  4. 内置的 HTTP 框架支持:NestJS 内置了对 Express.js 的支持,并提供了一组装饰器和工具,简化了处理 HTTP 请求和响应的过程。这使得开发者可以使用熟悉的 Express.js API,并且能够轻松地集成其他中间件和插件。

  5. 强大的路由系统:NestJS 提供了一个灵活且易于使用的路由系统,可以通过装饰器和装饰器链式调用的方式定义路由和请求处理程序。这种声明式的路由定义方式使得代码更加清晰和可读,并且提供了更好的可维护性。

  6. 可插拔的中间件支持:NestJS 支持使用中间件来处理请求和响应,这使得开发者可以在请求处理过程中添加各种功能,例如身份验证、日志记录、错误处理等。NestJS 的中间件系统支持全局中间件和局部中间件,提供了灵活的配置选项。

  7. 良好的生态系统:NestJS 拥有庞大的生态系统,提供了许多官方和第三方的模块、插件和工具,可以方便地集成和使用其他流行的库和框架,例如 TypeORM、GraphQL、WebSocket 等。

总的来说,NestJS 是一个功能强大、可扩展的服务器端应用程序框架,它结合了 TypeScript 的优势、模块化架构、依赖注入、内置的 HTTP 框架支持和强大的路由系统等特点。这些特点使得开发者可以更轻松地构建可维护、可测试和高效的应用程序。

# 说说 Nestjs 的核心设计思想和原理

NestJS 的核心设计思想是使用现代化的面向对象编程(OOP)和函数式编程(FP)的概念来构建可扩展、模块化和可测试的应用程序。以下是一些与 NestJS 相关的核心设计思想和原理:

  1. 模块化架构:NestJS 使用模块化的架构来组织应用程序。每个功能相关的组件(例如控制器、服务、中间件等)都被组织在一个模块中。模块之间可以相互依赖和引用,从而构建出一个完整的应用程序。这种模块化的设计使得应用程序的组织和维护更加简单和可扩展。

  2. 依赖注入(DI):NestJS 提供了一个强大的依赖注入容器,用于管理应用程序中的组件和服务。依赖注入使得代码解耦、可测试性更强,并且有助于实现可扩展的架构。通过将依赖关系的创建和解析委托给 DI 容器,NestJS 可以自动处理组件之间的依赖关系,并在需要时创建和注入相应的实例。

  3. 路由系统:NestJS 提供了一个灵活且易于使用的路由系统,用于处理 HTTP 请求。开发者可以使用装饰器和装饰器链式调用的方式来定义路由和请求处理程序。这种声明式的路由定义方式使得代码更加清晰和可读,并且提供了更好的可维护性。NestJS 的路由系统支持常见的 HTTP 方法(GET、POST、PUT、DELETE 等),并且可以处理各种路由参数和查询参数。

  4. 中间件支持:NestJS 支持使用中间件来处理请求和响应。中间件可以在请求处理过程中添加各种功能,例如身份验证、日志记录、错误处理等。NestJS 的中间件系统支持全局中间件和局部中间件,可以灵活配置和使用。

  5. 异步编程:NestJS 鼓励使用异步编程来提高应用程序的性能和可伸缩性。它提供了一些异步编程的机制,例如异步服务、异步控制器方法、异步中间件等。这些机制可以与 JavaScript 的异步特性(例如 Promise、async/await)结合使用,使开发者能够编写高效的异步代码。

  6. 强大的生态系统:NestJS 拥有庞大的生态系统,提供了许多官方和第三方的模块、插件和工具,可以方便地集成和使用其他流行的库和框架,例如 TypeORM、GraphQL、WebSocket 等。这些模块和插件可以通过简单的配置和集成,扩展 NestJS 的功能和能力。

通过这些核心设计思想和原理,NestJS 提供了一个强大且灵活的框架,使开发者能够构建可扩展、模块化和可测试的应用程序。它结合了现代化的编程概念和丰富的生态系统,为构建复杂的服务器端应用程序提供了便利和效率。

# Koa、Express 的核心设计思想和原理

Koa 和 Express 是两个流行的 Node.js Web 框架,它们有一些共同的核心设计思想和原理,同时也有一些区别。以下是它们的核心设计思想和原理的概述:

Express 的核心设计思想和原理:

  1. 中间件架构:Express 使用中间件架构来处理请求和响应。中间件是一个函数,可以访问请求对象(req)、响应对象(res)和下一个中间件函数(next)。每个请求都会经过一系列的中间件函数,每个中间件函数可以对请求和响应进行处理或者将控制权传递给下一个中间件函数。这种中间件架构使得开发者可以按照需要添加、删除或者修改中间件,从而实现灵活的请求处理逻辑。

  2. 路由系统:Express 提供了一个简单而灵活的路由系统,用于定义和处理不同的路由。开发者可以使用路由定义来匹配请求的 URL 和 HTTP 方法,并指定相应的处理函数。这使得开发者可以根据不同的 URL 和方法,定义不同的请求处理逻辑。

  3. 请求和响应对象:Express 提供了方便的请求对象(req)和响应对象(res),使开发者能够访问和操作请求和响应的各个方面,例如请求头、请求体、响应头、响应体等。这些对象提供了许多有用的方法和属性,使开发者能够更轻松地处理和操作请求和响应。

Koa 的核心设计思想和原理:

  1. 异步中间件流程控制:Koa 强调使用异步中间件来处理请求和响应,以提供更好的性能和可伸缩性。Koa 使用 async/await 或者返回 Promise 的中间件函数,使得中间件可以按照顺序执行异步操作,而不需要嵌套的回调函数。这种异步中间件流程控制使得代码更加清晰和易于理解。

  2. 上下文对象:Koa 引入了上下文对象(context),它是一个封装了请求和响应的对象。上下文对象提供了许多方便的方法和属性,使开发者能够更方便地访问和操作请求和响应的各个方面。上下文对象还提供了一些辅助方法,例如 throwassert,用于处理错误和断言。

  3. 中间件洋葱模型:Koa 的中间件执行顺序遵循洋葱模型。这意味着每个中间件函数可以在处理请求之前和之后执行一些操作,从而形成一个环绕式的执行流程。这种洋葱模型使得中间件可以在请求处理过程中执行前置操作和后置操作,例如身份验证、日志记录等。

总的来说,Express 和 Koa 都是基于中间件架构的 Node.js Web 框架,它们的核心设计思想和原理都围绕着中间件的概念展开。Express 更加简单和直接,注重灵活性和易用性,而 Koa 则更加注重异步编程和中间件流程控制,提供了更强大的异步处理能力。开发者可以根据自己的需求和偏好选择适合的框架来构建 Node.js Web 应用程序。

# 23、其他面试题

NodeJS有难度的面试题,你能答对几个? (opens new window)